package Shared;

import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map.Entry;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.namespace.QName;

@XmlRootElement(name = "Party")
public class Party implements Serializable {    
    private static final long serialVersionUID = 1L;
    
    @XmlTransient
    private static Tier2.PartyManagerImpl _manager = null;
    @XmlTransient
    public void setManager(Tier2.PartyManagerImpl manager) { _manager = manager; }
    
    private HashMap<Player, Integer> _players = new HashMap<Player, Integer>(); //players + score du joueur
    private HashMap<Card, Player> _cartes = new HashMap<Card, Player>(); //joueur + carte sur le tapis
    private Integer _statut = _Config.PARTY_ATTENTE;
    private int _nb_joueurs = 2;
    private int _id = -1;
    
    public Party() { }
    public Party(int nb_joueurs, int id) { _nb_joueurs = nb_joueurs; set_id(id);/* thread = new Thread(this);*/ }
    
    public int getNbPlayers() { return _players.size(); }
    public int getScore(Player player) { return _players.get(player); }
    public int getStatut() { return _statut; }
    
    //TODO afficher les actions et résultats dans la console des utilisateurs ?
    
    public Integer get_statut() { return _statut; }
	public void set_statut(Integer _statut) { this._statut = _statut; }

	public int get_id() { return _id; }
	public void set_id(int _id) { this._id = _id; }

	public HashMap<Player, Integer> getPlayers() { return _players;}
	public void setPlayers(HashMap<Player, Integer> _players) { this._players = _players;}

    /**
     * Ajoute le joueur à la partie s'il reste des place dispo
     * @param player
     * @return false si la partie est déjà démarrée
     */
    public boolean addPlayer(Player player) { 
	if(_statut!=_Config.PARTY_ATTENTE)
	    return false;
	
	_players.put(player, 0); 
	
	if(_players.size()==_nb_joueurs) {
	    _statut = _Config.PARTY_ENCOURS;
	    for(Player p : _players.keySet()) {
		_manager.callback(p.getCallback(), "La partie démarre");
	    }
	}
	return true;
    }
    
    /**
     * Un joueur quitte la partie : 
     * ses cartes sont distribuées au hasard aux autres joueurs
     * S'il n'en reste qu'un il gagne
     */
    public void quitterParty(Player player) {
	
	 //enleve le joueur des joueurs de la partie
	 _players.remove(player);
	    
	 //si l'utilisateur a une carte posée sur le tapis, on la retire
	 for(Entry<Card, Player> p : _cartes.entrySet()) {
	     	if(p.getValue()==player) {
	     	    _cartes.remove(p.getKey());
    		}
	 }
	
	//s'il ne reste plus qu'un seul joueur, il gagne et la partie est finie
	if(_players.size()-1==1) {  
	    finir();
	    
	//sinon on distribue aléatoirement ses cartes aux autres joueurs
	} else {
	    distribuer(player.getMain(), _players);
	}
    }
    
    /**
     * Répartit toutes les cartes du jeu entre les différents joueurs
     */
    public void distribuer() {
	ArrayList<Card> cartes = _Config.jeuDeCarte;	
	distribuer(cartes, _players);
    }
    private void distribuer(HashMap<Card, Player> cartes, ArrayList<Player> players) {
	ArrayList<Card> list = new ArrayList<Card>(cartes.keySet());
	distribuer(list, players);
    }
    private void distribuer(ArrayList<Card> cartes, HashMap<Player, Integer> players) {
	ArrayList<Player> list = new ArrayList<Player>(players.keySet());
	distribuer(cartes, list);
    }
    
    /**
     * Distribution aleatoire des cartes entre les players
     * @param cartes
     * @param players
     */
    private void distribuer(ArrayList<Card> cartes, ArrayList<Player> players) {
    	//melange du tableau de cartes
    	Collections.shuffle(cartes);
    	
    	//distribution des cartes aux joueurs.
    	int cptPlayer = 0;
    	for(Card carte : cartes){
    		players.get(cptPlayer).addCarte(carte);
    		if(cptPlayer == players.size()-1) {
    			cptPlayer = 0;
    		} else {
    			cptPlayer++;
    		}
    		// System.out.println("Carte n°"+carte.getName()+" donné au joueur "+players.get(cptPlayer).getName());
    	}
    }
    
    /**
     * Ajoute une carte au tapis
     * @param player
     * @param carte
     */
    public void jouer(Player player, Card carte) {
	_cartes.put(carte, player);
	
	if(_cartes.size()==getNbPlayers()) {
	    comparer();
	}
    }
    
    /**
     * Compare les cartes jouées et les donne au(x) perdant(s) - sauf match nul
     */
    public void comparer() {
	
	//détermine le ou les perdants
	int value = 0; ArrayList<Player> min = new ArrayList<Player>();
	for(Entry<Card, Player> p : _cartes.entrySet()) {
	    Card carte = p.getKey();
	    
	    if(value==0 || value>=carte.getPoids()) {		
		if(value>carte.getPoids()) { min.clear(); } //un nouveau minimum : on vide la liste des perdants
		
		value = carte.getPoids();
		min.add(p.getValue());
	    }
	}
	
	//match nul
	if(min.size()==getNbPlayers()) {
	    return;
	}
	
	//enleve les cartes du tapis de la main des joueurs puis les distribue entre les perdants
	for(Entry<Card, Player> item : _cartes.entrySet()) {
	    item.getValue().removeCarte(item.getKey());
	}
	distribuer(_cartes, min);
	_cartes.clear();
	
	if(isFini()) {
	    finir();
	}
    }
    
    /**
     * Gere la fin de partie (historisation + information utilisateur)
     */
    public void finir() {
	_statut = _Config.PARTY_FINIE;

	int score = _Config.jeuDeCarte.size() + getNbPlayers(); //le gagnant gagne 5 points + le nombre de joueurs (à la fin)
	int nbCard = _Config.jeuDeCarte.size();
	
	//si le joueur a toutes les cartes ou qu'il ne reste plus qu'un seul joueur, il gagne
	for(Entry<Player, Integer> item : _players.entrySet()) {
	    if(item.getKey().getMain().size()==nbCard) {
    	    	item.setValue(score);
    	    	item.getKey().addScore(score);
	    }
	}	
    }
    
    /**
     * Verifie si la partie est finie ou non
     * @return vrai si un joueur a l'ensemble des cartes
     */
    public boolean isFini() {
	int nbCard = _Config.jeuDeCarte.size();
	for(Player p : _players.keySet()) {
	    if(p.getNbCartes()==nbCard) {
		return true;
	    }
	}
	return false;
    }

    
    public boolean pretAJouer(){
    	//if(_players.size() == _nb_joueurs){ return true; }else { return false; }
    	while(this._statut != Shared._Config.PARTY_ENCOURS){
    		//attente
    		System.out.println("Début de partie en attente.");
    	}
    	return true;
    }
    /*
    @Override
	public void run() {
		boolean finDePartie = false;
    	while(!finDePartie){
	    	if(pretAJouer()){
	    		System.out.println("les joueurs sont pret!");
	    		System.out.println("La partie va commencer!");
	    		try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
	    		System.out.println("*Distribution*");
	    		this.distribuer();
	    		System.out.println("*Fin Distribution*");
	    		try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
	    		System.out.println("*Debut de la partie*");
	    	}
	    	int cpt = 1;
	    	while(!isFini()){
		    	System.out.println("--Tour n°"+cpt+"--");
		    	for(Entry<Player, Integer> entry : _players.entrySet()){
		    		Player player = entry.getKey();
		    		Card carte = player.getHasardCard();
		    		System.out.println("Joueur "+player.getName()+" joue la carte "+carte.getName());
		    		jouer(player, carte);
		    	}
		    	try {Thread.sleep(5000);} 
		    	 catch (InterruptedException e) {e.printStackTrace();}
		    	cpt++;
	    	}
	    	
	    	if(isFini()){
	    		finir();
	    		finDePartie = true;
	    	}
    	}
	}
    
    /*
    public void jeu(){
    	boolean finDePartie = false;
    	while(!finDePartie){
	    	if(pretAJouer()){
	    		System.out.println("les joueurs sont pret!");
	    		System.out.println("La partie va commencer!");
	    		try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
	    		System.out.println("*Distribution*");
	    		this.distribuer();
	    		System.out.println("*Fin Distribution*");
	    		try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
	    		System.out.println("*Debut de la partie*");
	    	}
	    	int cpt = 1;
	    	while(!isFini()){
		    	System.out.println("--Tour n°"+cpt+"--");
		    	for(Entry<Player, Integer> entry : _players.entrySet()){
		    		Player player = entry.getKey();
		    		Card carte = player.getHasardCard();
		    		System.out.println("Joueur "+player.getName()+" joue la carte "+carte.getName());
		    		jouer(player, carte);
		    	}
		    	try {Thread.sleep(5000);} 
		    	 catch (InterruptedException e) {e.printStackTrace();}
		    	cpt++;
	    	}
	    	
	    	if(isFini()){
	    		finir();
	    		finDePartie = true;
	    	}
    	}
    }
    */
   

    // +----------------------------------------------------------------------+
    // | Serializer
    // +----------------------------------------------------------------------+
	public static String serialize(Party party) {
	    try {
        	StringWriter writer = new StringWriter();
        	JAXBContext context = JAXBContext.newInstance(Party.class);
        	Marshaller m = context.createMarshaller();
        	m.marshal(new JAXBElement(new QName(Party.class.getSimpleName()), Party.class, party), writer);
        	return writer.toString();
	    } catch(Exception e) {
		e.printStackTrace();	
	    }
	    return "";
	}
	public static Party deserialize(String input) {
	    try {
        	JAXBContext context = JAXBContext.newInstance(Party.class);
        	Unmarshaller m = context.createUnmarshaller();
        	return (Party)m.unmarshal(new StringReader(input));
	    } catch(Exception e) {
		e.printStackTrace();
	    }
	    return null;
	}
}
